#include "dfs.h"
#include "dfs_inner.h"
#if CONFIG_OS_ENABLE
#include "os_sal.h"
#endif

/* 虚拟文件系统-dfs */


#if (CONFIG_DFS_ENABLE)

/* define */
#ifdef TAG
#undef TAG
#endif
#define TAG "DFS"

typedef struct
{
    uint32_t maxfd;
    struct dfs_fd **fds;
} _dfs_fdtbl_t;

/* static var */
static _dfs_fdtbl_t dfs_fdtbl_t;
#ifdef CONFIG_DFS_WORKDIR
static char dfs_working_dir[DFS_PATH_MAX] = {"/"};
#endif
#if CONFIG_OS_ENABLE
static os_mutex_handle_t dfs_mutex = NULL;
#endif

/* export var */
const struct dfs_filesystem_ops *dfs_fs_ops_tbl[DFS_FILESYSTEM_TYPES_MAX];
struct dfs_filesystem dfs_fs_tbl[DFS_FILESYSTEMS_MAX];




/* static func */

static _dfs_fdtbl_t *dfs_get_fdtable(void)
{
    return &dfs_fdtbl_t;
}

static int dfs_fd_alloc(_dfs_fdtbl_t *fdt, int startfd)
{
    int idx;

    /* find an empty fd entry */
    for (idx = startfd; idx < (int)fdt->maxfd; idx++)
    {
        if (fdt->fds[idx] == NULL) break;
        if (fdt->fds[idx]->ref_count == 0) break;
    }

    /* allocate a larger FD container */
    if (idx == fdt->maxfd && fdt->maxfd < DFS_FD_MAX)
    {
        int cnt, index;
        struct dfs_fd **fds;

        /* increase the number of FD with 4 step length */
        cnt = fdt->maxfd + 4;
        cnt = cnt > DFS_FD_MAX ? DFS_FD_MAX : cnt;

        fds = (struct dfs_fd **)dfs_mem_realloc(fdt->fds, cnt * sizeof(struct dfs_fd *));
        if (fds == NULL) goto __exit; /* return fdt->maxfd */

        /* clean the new allocated fds */
        for (index = fdt->maxfd; index < cnt; index ++)
        {
            fds[index] = NULL;
        }

        fdt->fds   = fds;
        fdt->maxfd = cnt;
    }

    /* allocate  'struct dfs_fd' */
    if (idx < (int)fdt->maxfd && fdt->fds[idx] == NULL)
    {
        fdt->fds[idx] = (struct dfs_fd *)dfs_mem_calloc(1, sizeof(struct dfs_fd));
        if (fdt->fds[idx] == NULL) idx = fdt->maxfd;
    }

__exit:
    return idx;
}


/* export func */

int dfs_init(void)
{
    static uint8_t flag_init = 0;

    if (flag_init) return 0;

#if CONFIG_OS_ENABLE
    dfs_mutex = os_mutex_create();
    if (dfs_mutex == NULL)
    {
        LOG_E(TAG, "dfs_mutex create fail\r\n");
        return -1;
    }
#endif

    memset(dfs_fs_ops_tbl, 0x00, sizeof(dfs_fs_ops_tbl));   // 清除文件系统操作句柄
    memset(dfs_fs_tbl, 0x00, sizeof(dfs_fs_tbl));           // 清除文件系统记录
    memset(&dfs_fdtbl_t, 0x00, sizeof(dfs_fdtbl_t));        // 清除fd文件句柄

#ifdef CONFIG_DFS_WORKDIR
    memset(dfs_working_dir, 0x00, sizeof(dfs_working_dir));
    dfs_working_dir[0] = '/';
#endif

    flag_init = 1;

    return 0;
}

void dfs_lock(void)
{
#if CONFIG_OS_ENABLE
    os_mutex_take(dfs_mutex, OS_WAIT_MAX);
#endif
}

void dfs_unlock(void)
{
#if CONFIG_OS_ENABLE
    os_mutex_release(dfs_mutex);
#endif
}

/**
 * @brief 创建文件描述符
 * 
 * @return int 
 */
int dfs_fd_new(void)
{
    int idx;
    _dfs_fdtbl_t *fdt;

    fdt = dfs_get_fdtable();

    dfs_lock();

    idx = dfs_fd_alloc(fdt, 0);  // 查找一个空的fd入口
    if (idx == fdt->maxfd)
    {
        dfs_unlock();
        LOG_E(TAG, "fd max");
        return idx + DFS_FD_OFFSET;
    }
    fdt->fds[idx]->ref_count = 1;
    fdt->fds[idx]->magic = DFS_FD_MAGIC;

    dfs_unlock();

    return idx + DFS_FD_OFFSET;
}

/**
 * @brief 由fd文件描述符获取文件句柄
 * 
 * @param fd 
 * @return struct dfs_fd* 
 */
struct dfs_fd *dfs_fd_get(int fd)
{
    struct dfs_fd *obj;
    _dfs_fdtbl_t *fdt;

    fdt = dfs_get_fdtable();
    fd -= DFS_FD_OFFSET;
    if (fd < 0 || fd >= fdt->maxfd)
    {
        LOG_E(TAG, "fd get fail");
        return NULL;
    }

    dfs_lock();

    obj = fdt->fds[fd];
    if (obj == NULL || obj->magic != DFS_FD_MAGIC)
    {
        dfs_unlock();
        LOG_E(TAG, "fd get is null");
        return NULL;
    }
    obj->ref_count++;

    dfs_unlock();

    return obj;
}

/**
 * @brief put the file descriptor
 * 
 * @param obj 
 */
void dfs_fd_put(struct dfs_fd *obj)
{
    int i;
    _dfs_fdtbl_t *fdt;

    if (obj == NULL)
    {
        LOG_E(TAG, "obj null in dfs_fd_put");
        return ;
    }

    dfs_lock();

    obj->ref_count--;
    if (obj->ref_count == 0)
    {
        fdt = dfs_get_fdtable();
        for (i = 0; i < fdt->maxfd; i++)
        {
            if (fdt->fds[i] == obj)
            {
                dfs_mem_free(obj);
                fdt->fds[i] = NULL;
                break;
            }
        }
        
    }

    dfs_unlock();
}

/**
 * @brief return a sub-path name under directory
 * 
 * @param directory the parent directory
 * @param filename the filename
 * @return const char* the subdir pointer in filename
 */
const char *dfs_subdir(const char *directory, const char *filename)
{
    const char *dir;

    /* it's a same path */
    if (strlen(directory) == strlen(filename)) return NULL;

    dir = filename + strlen(directory);
    if ((*dir != '/') && (dir != filename))
    {
        dir --;
    }

    return dir;
}

/**
 * @brief 格式化输出文绝对路径
 * 
 * @param directory 路径
 * @param filename 文件名
 * @return char* 绝对路径+文件名
 */
char *dfs_normalize_path(const char *directory, const char *filename)
{
    char *fullpath;
    char *dst0, *dst, *src;

    if (filename == NULL)
    {
        LOG_E(TAG, "filename is null");
        return NULL;
    }

    if (directory == NULL) /* shall use working directory */
    {
        directory = &dfs_working_dir[0];
    }

    if (filename[0] != '/') /* it's a absolute path, use it directly */
    {
        fullpath = (char *)dfs_mem_malloc(strlen(directory) + strlen(filename) + 2);
        if (fullpath == NULL) return NULL;

        /* 合并路径与文件名 */
        snprintf(fullpath, strlen(directory) + strlen(filename) + 2, "%s/%s", directory, filename);
    }
    else
    {
        fullpath = dfs_mem_strdup(filename); /* copy string */
        if (fullpath == NULL) return NULL;
    }

    src = fullpath;
    dst = fullpath;
    dst0 = dst;
    while (1)
    {
        char c = *src;

        if (c == '.')
        {
            if (!src[1]) src ++; /* '.' and ends */
            else if (src[1] == '/')
            {
                /* './' case */
                src += 2;
                while ((*src == '/') && (*src != '\0')) src ++;
                continue;
            }
            else if (src[1] == '.')
            {
                if (!src[2])
                {
                    /* '..' and ends case */
                    src += 2;
                    goto up_one;
                }
                else if (src[2] == '/')
                {
                    /* '../' case */
                    src += 3;
                    while ((*src == '/') && (*src != '\0')) src ++;
                    goto up_one;
                }
            }
        }

        /* copy up the next '/' and erase all '/' */
        while ((c = *src++) != '\0' && c != '/') *dst ++ = c;

        if (c == '/')
        {
            *dst ++ = '/';
            while (c == '/') c = *src++;
            src --;
        }
        else if (!c) break;

        continue;

up_one:
        dst --;
        if (dst < dst0)
        {
            dfs_mem_free(fullpath);
            return NULL;
        }
        while (dst0 < dst && dst[-1] != '/') dst --;
    }

    *dst = '\0';

    /* remove '/' in the end of path if exist */
    dst --;
    if ((dst != fullpath) && (*dst == '/')) *dst = '\0';

    /* final check fullpath is not empty, for the special path of lwext "/.." */
    if ('\0' == fullpath[0])
    {
        fullpath[0] = '/';
        fullpath[1] = '\0';
    }

    return fullpath;
}

/**
 * @brief 当前文件是否已打开
 * 
 * @param pathname 文件路径名
 * @return int 0.ok  -1.fail
 */
int dfs_fd_is_open(const char *pathname)
{
    char *fullpath;
    char *mountpath;
    struct dfs_filesystem *fs;
    struct dfs_fd *fd;
    _dfs_fdtbl_t *fdt;
    int i;

    fdt = dfs_get_fdtable();
    fullpath = dfs_normalize_path(NULL, pathname);
    if (fullpath)
    {
        fs = dfs_fs_lookup(fullpath);
        if (fs == NULL)
        {
            LOG_E(TAG, "fs lookup null");
            dfs_mem_free(fullpath);
            return -1;
        }

        /* get file path name under mounted file system */
        if (fs->path[0] == '/' && fs->path[1] == '\0')
        {
            mountpath = fullpath;
        }
        else
        {
            mountpath = fullpath + strlen(fs->path);
        }

        dfs_unlock();
        for (i = 0; i < fdt->maxfd; i++)
        {
            fd = fdt->fds[i];
            if (fd == NULL || fd->fops == NULL || fd->path == NULL) continue;
            if (fd->fs == fs && (strcmp(fd->path, mountpath) == 0))
            {
                dfs_mem_free(fullpath);
                dfs_unlock();
                return 0;
            }
        }
        dfs_unlock();
        dfs_mem_free(fullpath);
    }

    return -1;
}

void dfs_set_errno(int err)
{
    errno = err;
}


struct dfs_filesystem *dfs_get_fs_tbl(uint32_t idx)
{
    if (idx >= DFS_FILESYSTEMS_MAX) return NULL;
    return &dfs_fs_tbl[idx];
}

void dfs_set_fs_tbl(uint32_t idx, const struct dfs_filesystem *fs)
{
    if (idx >= DFS_FILESYSTEMS_MAX)
    {
        LOG_E(TAG, "fs_tbl set fail (%d)", idx);
    }
    memcpy(&dfs_fs_tbl[idx], fs, sizeof(struct dfs_filesystem));
}

const struct dfs_filesystem_ops *dfs_get_fs_ops_tbl(uint32_t idx)
{
    if (idx >= DFS_FILESYSTEM_TYPES_MAX) return NULL;
    return dfs_fs_ops_tbl[idx];
}

void dfs_set_fs_ops_tbl(uint32_t idx, const struct dfs_filesystem_ops *ops)
{
    if (idx >= DFS_FILESYSTEM_TYPES_MAX)
    {
        LOG_E(TAG, "fs_ops set fail (%d)", idx);
    }
    dfs_fs_ops_tbl[idx] = ops;
}


char *dfs_get_working_dir(void)
{
    return dfs_working_dir;
}

int dfs_list_fd(void)
{
    int index;
    _dfs_fdtbl_t *fd_table;

    fd_table = dfs_get_fdtable();
    if (!fd_table) return -1;

    dfs_lock();

    // printf("fd_table->maxfd = %d\r\n", fd_table->maxfd);
    printf("fd type    ref magic  path\r\n");
    printf("-- ------  --- ----- ------\r\n");
    for (index = 0; index < (int)fd_table->maxfd; index ++)
    {
        struct dfs_fd *fd = fd_table->fds[index];

        if (fd && fd->fops)
        {
            printf("%2d ", index + DFS_FD_OFFSET);
            if (fd->type == FT_DIRECTORY)    printf("%-7.7s ", "dir");
            else if (fd->type == FT_REGULAR) printf("%-7.7s ", "file");
            else if (fd->type == FT_SOCKET)  printf("%-7.7s ", "socket");
            else if (fd->type == FT_USER)    printf("%-7.7s ", "user");
            else printf("%-8.8s ", "unknown");
            printf("%3d ", fd->ref_count);
            printf("%04x  ", fd->magic);
            if (fd->path)
            {
                printf("%s\r\n", fd->path);
            }
            else
            {
                printf("\r\n");
            }
        }
    }
    dfs_unlock();

    return 0;
}


#endif // CONFIG_DFS_ENABLE

